package pl.edu.agh.neuraleconomy.core.ta.advice;

import java.util.Arrays;
import java.util.Date;
import java.util.List;

import lombok.Setter;

import org.apache.log4j.Logger;

import pl.edu.agh.neuraleconomy.common.utils.DateUtils;
import pl.edu.agh.neuraleconomy.core.nn.CoreUtils;
import pl.edu.agh.neuraleconomy.core.nn.NetworkStructure;
import pl.edu.agh.neuraleconomy.core.nn.NeuralNetworkService;
import pl.edu.agh.neuraleconomy.core.nn.SimpleNetwork;
import pl.edu.agh.neuraleconomy.core.nn.transformer.DailyChangeDataTransformer;
import pl.edu.agh.neuraleconomy.core.nn.transformer.IDataTransformer;
import pl.edu.agh.neuraleconomy.model.exchange.Company;
import pl.edu.agh.neuraleconomy.model.exchange.Exchange;
import pl.edu.agh.neuraleconomy.persistence.base.DaoProvider;
import pl.edu.agh.neuraleconomy.persistence.exchange.ExchangeDao;

public class PredictionAdvisor implements IAdvisor {
	private Logger logger = Logger.getLogger(getClass());
	private NeuralNetworkService networkService = new NeuralNetworkService();

	@Setter
	private int predictedSessions = 5;
	@Setter
	private NetworkStructure structure = new NetworkStructure();
	@Setter
	private IDataTransformer transformer = new DailyChangeDataTransformer();

	public PredictionAdvisor() {
	}

	public Advice getAdvice(Company company, Date date) {
		try {
			date = DateUtils.addDays(date, 1); // we want to predict a future
			
			double[] predictionData = getPredictionData(company, date);
			logger.debug(String.format("Predicted data: %s", Arrays.toString(predictionData)));

			double trend = calculateTrend(predictionData);
			double maxTrend = maxTrend(predictionData);
			Advice advice = getAdvice(company, trend, maxTrend);

			logger.debug(String.format("Trend: %s, MaxTrend: %s, Advice: %s", String.valueOf(trend), String.valueOf(maxTrend),
					advice.toString()));

			return advice;
		} catch (Exception e) {
			logger.error(e.getMessage());
			return null;
		}
	}

	private Advice getAdvice(Company company, double trend, double maxTrend) {
		AdviceType type = null;
		int certainty = (int) ((Math.abs(trend) * 100) / maxTrend);

		if (trend < 0) {
			type = AdviceType.SELL;
		} else if (trend > 0) {
			type = AdviceType.BUY;
		} else {
			type = AdviceType.STAY;
			certainty = 0;
		}

		return new Advice(company, type, certainty);
	}

	private double calculateTrend(double[] predictionData) {
		int trendLen = 1;
		double trendSignum = Math.signum(predictionData[0]);

		for (int i = 1; i < predictionData.length; i++) {
			double tempTrend = Math.signum(predictionData[i]);
			if (trendSignum != tempTrend && tempTrend != 0.0) {
				break;
			}

			trendLen++;
		}

		return trendLen * trendSignum;
	}

	private double maxTrend(double[] predictionData) {
		return predictionData.length;
	}

	private double[] getPredictionData(Company company, Date date) {
		SimpleNetwork network = new SimpleNetwork(structure);
		double[] trainData = CoreUtils.toDoubleArray(networkService.getTrainData(company.getId(), date));
		networkService.trainNetworkWithData(network, trainData, transformer);
		double[] inputData = getPredictionInputData(network, company.getId(), date, transformer);
		double[] predictedData = networkService.predictForCompany(network, inputData, predictedSessions, transformer, false);

		return predictedData;
	}

	protected double[] getPredictionInputData(SimpleNetwork network, Long companyId, Date dateFrom, IDataTransformer transformer) {
		ExchangeDao dao = DaoProvider.getExchangeDao();

		List<Exchange> exchanges = dao.getLatestByCompanyExcludeDate(companyId, dateFrom,
				(long) transformer.getInputLenForOutputLen(network.getInput()));
		double data[] = CoreUtils.toDoubleArray(exchanges);

		return data;
	}

}
